package warthog.parsegen;

import java.io.*;
import java.util.*;

import warthog.cradle.Descent;
import warthog.cradle.JavaClass;

public class MetaParser extends Descent {
	private Generator gen;
	private Map<String, SemanticClass> types = new HashMap<String, SemanticClass>();
	GeneratorData data;
	private JavaClass javaClass;

	public MetaParser(File path) throws IOException {
		super(new FileReader(path));
		javaClass = new JavaClass(path);
		data = new GeneratorData();
		gen = new Generator();
	}
	public void run() throws IOException {
		parseHeader();
		matchSeparator();
		parseGrammar();
		gen.build();
		gen.writeAllCode(javaClass, data);
		javaClass.writeFile();
	}
	private void parseHeader() throws IOException {
		while (true) {
			if (inWhite()||inEOL()) skipLine();
			else if (inAlpha()) {
				String optionName = parseWord("option");
				skipSpace();
				if ("package".equals(optionName)) {
					javaClass.packageName = collectLine().toString();
					matchEOL();
				}
				else if ("import".equals(optionName)) {
					javaClass.preamble.append("import ");
					javaClass.preamble.append(collectWholeLine());
				}
				else if ("abstract".equals(optionName)) {
					javaClass.isAbstract = true;
					matchEOL();
				}
				else if ("final".equals(optionName)) {
					javaClass.isFinal = true;
					matchEOL();
				}
				else if ("public".equals(optionName)) {
					javaClass.isPublic = true;
					matchEOL();
				}
				else if ("type".equals(optionName)) {
					parseTypeDeclaration();
				}
				else if ("left".equals(optionName)) {
					gen.assoc(Assoc.LEFT, parseTokens());
					matchEOL();
				}
				else if ("right".equals(optionName)) {
					gen.assoc(Assoc.RIGHT, parseTokens());
					matchEOL();
				}
				else if ("non".equals(optionName)) {
					gen.assoc(Assoc.NON, parseTokens());
					matchEOL();
				}
				else if ("token".equals(optionName)) {
					for (String s:parseTokens()) {
						gen.sym(s);
					}
					matchEOL();
				}
				else {
					error("option "+optionName+" not understood.");
				}
			}
			else break;
		}
	}
	private void parseTypeDeclaration() throws IOException {
		Collection<String> tokens = parseTokens();
		match(':');
		skipSpace();
		String type=collectLine().toString();
		for (String t:tokens) setType(t,type);
		matchEOL();
	}
	private Collection<String> parseTokens() throws IOException {
		ArrayList<String> tokens = new ArrayList<String>();
		while (inAlpha()) {
			tokens.add(parseWord("token"));
			skipSpace();
		}
		return tokens;
	}
	private void setType(String symName, String javaType) {
		if (!types.containsKey(javaType)) types.put(javaType, new SemanticClass(javaType));
		Sym s = gen.sym(symName);
		// if (s.semanticClass != null) foo ;
		s.semanticClass = types.get(javaType);
	}
	private void matchSeparator() throws IOException {
		match('%');
		match('%');
		matchEOL();
	}
	private void parseGrammar() throws IOException {
		while (!atEOF()) {
			if (is('%')) {
				matchSeparator();
				parsePostamble();
				return;
			} else if (inAlpha()) {
				parseNonterminal();
			} else if (inEOL()) {
				matchEOL();
			}
			else error("Confused");
		}
	}
	String head;
	private void parseNonterminal() throws IOException {
		head = parseWord("nonterminal");
		skipSpace();
		if (maybe(':')) {
			skipSpace();
			String type=collectLine().toString();
			setType(head, type);
		}
		matchEOL();
		while (maybe('\t')) {
			parseRule(head);
		}
	}
	private void parseRule(String head) throws IOException {
		Collection<String> rhs = parseTokens();
		if (rhs.isEmpty() && inEOL()) {
			// If the line is completely whitespace, it doesn't count as a rule.
			matchEOL();
			return;
		}
		Rule rule = gen.rule(head, rhs);
		rule.line = lineNr;
		if (maybe(':')) {
			rule.note = collectWholeLine().toString();
		} else matchEOL();
	}
	private void parsePostamble() throws IOException {
		while(!atEOF()) {
			javaClass.code.append((char)look); read();
		}
	}

}
